using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Drawing;
using System.Globalization;
using System.Management;
using System.Net;
using System.Runtime.InteropServices;
using System.ServiceProcess;
using System.Text;
using System.Windows.Forms;

namespace ServerState
{
    public partial class MainForm : Form
    {
        // List of all services. Only display the service state when the service is installed
        // Can be overwritten by command line parameters
        private static string[] serviceNames = new string[] {
            "IpPbxSrv", "IpPbxCDS", "IpPbxReport", "HwDSrv", "IpPbxGate", "LinkMgr",
            "PhoneController",  // Up to version V7.0
            "PhoneMgr", "ConferenceMgr", "QueueManager", "IpPbxMEM", 
            "FaxSrv", "FaxPrt", // Up to version V7.0
            "IpPbxFaxSrv", "IpPbxFaxPrtGate", // From version V7.0
            "IpPbxMonitor",
            "IpPbx3pcc", // From Version V9.0
            "MSSQLSERVER", "MSSQL$SQLEXPRESS" };

        // Extended service controller class
        private class ServiceControllerEx : ServiceController
        {
            private ServiceControllerStatus? lastStatus = null;

            public ServiceControllerEx() : base() { }
            public ServiceControllerEx(string name) : base(name) { }
           
            /// <summary>
            /// State of service changed since last function call
            /// </summary>
            /// <returns>Returns true when service state is changed</returns>
            public bool StatusChanged()
            {
                this.Refresh();

                if (this.Status == this.lastStatus)
                    return false;

                this.lastStatus = this.Status;
                return true;
            }

            /// <summary>
            ///  Is service is disabled
            /// </summary>
            /// <returns>Returns true when the service is disabled</returns>
            /// <remarks>
            /// Method uses WMI because there is no .NET function for this. Using of native methods
            /// like QueryServiceConfig() is difficult, because the ServiceControl.ServiceHandle is
            /// invalid, when the current user is not an administrator.
            /// </remarks>
            public bool IsDisabled()
            {
                string wmiQuery = @"SELECT * FROM Win32_Service WHERE Name='" + this.ServiceName + @"'";
                ManagementObjectSearcher searcher = new ManagementObjectSearcher(wmiQuery);

                using (ManagementObjectCollection.ManagementObjectEnumerator iter = searcher.Get().GetEnumerator())
                {
                    iter.MoveNext();
                    return iter.Current["StartMode"].ToString() == "Disabled";
                }
            }
        }

        // Extended Performance Counter Class
        private class PerformanceCounterEx
        {
            private long? lastValue = null;
            private PerformanceCounter perfCount = null;

            /// <summary>
            /// 
            /// </summary>
            /// <param name="categoryName">
            /// The name of the performance counter category (performance object) with which
            //  this performance counter is associated.
            /// <param name="counterName">The name of the performance counter</param>
            /// <param name="counterDescription">A description of this counter</param>
            public PerformanceCounterEx(string categoryName, string counterName, string counterDescription = null)
            {
                this.perfCount = new PerformanceCounter(categoryName, counterName);
                this.Description =  counterDescription ?? counterName;
            }

            /// <summary>
            /// A description of this counter
            /// </summary>
            public string Description { get; private set; }

            /// <summary>
            /// Is counter is changed
            /// </summary>
            /// <returns>Returns true when the counter value is changed</returns>
            public bool Changed()
            {
                if (this.lastValue == this.perfCount.RawValue)
                    return false;

                this.lastValue = this.perfCount.RawValue;
                return true;
            }

            /// <summary>
            /// Gets the the raw, or uncalculated, value of this counter.
            /// </summary>
            public long RawValue
            {
                get { return this.perfCount.RawValue; }
            }
        }

        // List of current installed services
        private List<ServiceControllerEx> serviceList = null;

        // List of displayed performance counters
        private List<PerformanceCounterEx> perfCounterList = null;

        // Timer tick counter
        private int timerTickCount;

        /// <summary>
        /// Constructor of the main form. 
        /// Set text of all static (not changed during runtime) fields (machine name, IP addresses, 
        /// current user) and start the timer.
        /// </summary>
        public MainForm()
        {
            InitializeComponent();

            this.UpdateBackgroundColor();
            this.InitializeServiceList();
            
            #region Fill the list view
            {
                // Add this items to the first list view group
                ListViewGroup lvGroup = this.listViewMain.Groups[0];
                ListView.ListViewItemCollection lvCol = this.ListViewColumn.ListView.Items;

                // Clear the list view
                lvCol.Clear();

                // Write the computer name
                lvCol.Add(new ListViewItem("Computer Name:", lvGroup)).
                    SubItems.Add(Environment.MachineName);

                // Write the DNS name when the machine name is different
                if (Dns.GetHostName().ToUpperInvariant() !=
                    Environment.MachineName.ToUpperInvariant())
                {
                    lvCol.Add(new ListViewItem("DNS Name:", lvGroup)).
                        SubItems.Add(Dns.GetHostName());
                }

                // Write the IP Addresses
                IPHostEntry IPs = Dns.GetHostEntry(Dns.GetHostName());
                string ipList = "";
                string ipDesc = (IPs.AddressList.Length > 1) ? "IP Addresses:" : "IP Address:";
                foreach (IPAddress IP in IPs.AddressList)
                {
                    if (IP.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork)
                    {
                        if (ipList.Length != 0)
                            ipList += ", ";

                        ipList += IP;
                    }
                }
                lvCol.Add(new ListViewItem(ipDesc, lvGroup)).SubItems.Add(ipList);

                // Write the user name
                string userName = Environment.UserName;
                if (Environment.UserDomainName != Environment.MachineName)
                {
                    // Display a domain account as DOMAIN\USER
                    userName = string.Format(CultureInfo.InvariantCulture, "{0}\\{1}", Environment.UserDomainName, userName);
                }
                lvCol.Add(new ListViewItem("Current User:", lvGroup)).
                    SubItems.Add(userName);

                // Write IpPbx version information when found
                string ipPbxOemName = MainForm.GetIpPbxMsiProductInfo("InstalledProductName");
                string ipPbxVersion = MainForm.GetIpPbxMsiProductInfo("VersionString");
                string ipPbxLanguage = MainForm.GetIpPbxMsiProductInfo("Language");

                if (!string.IsNullOrEmpty(ipPbxOemName) && !string.IsNullOrEmpty(ipPbxVersion) &&
                     !string.IsNullOrEmpty(ipPbxLanguage))
                {
                    CultureInfo ci = new CultureInfo(Convert.ToInt32(ipPbxLanguage, CultureInfo.InvariantCulture));
                    string product = string.Format(CultureInfo.InvariantCulture, "{0}:", ipPbxOemName);
                    string version = string.Format(CultureInfo.InvariantCulture, "v{0} ({1})", ipPbxVersion, ci.Parent.NativeName);

                    lvCol.Add(new ListViewItem(product, lvGroup)).SubItems.Add(version);

                    this.listViewMain.Groups[2].Header = ipPbxOemName;
                }

                // Write the service states
                UpdateServiceState();
            }
            #endregion

            // Start the timer
            this.timer.Start();
        }

        /// <summary>
        /// Initialize the list of installed services
        /// </summary>
        private void InitializeServiceList()
        {
            this.serviceList = new List<ServiceControllerEx>();

            List<string> services = new List<string>();
            foreach (string s in MainForm.serviceNames)
            {
                services.Add(s);
            }

            // Parse the command line 
            // First entry is the executable name and will be ignored by the loop
            if (Environment.GetCommandLineArgs().Length > 1)
            {
                // When the first command line parameter is +, then combine the
                // command line parameter with the default services
                if (Environment.GetCommandLineArgs()[1] == @"+")
                {
                    for (int i = 2; i < Environment.GetCommandLineArgs().Length; i++)
                        services.Add(Environment.GetCommandLineArgs()[i]);
                }
                else
                {
                    services.Clear();
                    for (int i = 1; i < Environment.GetCommandLineArgs().Length; i++)
                        services.Add(Environment.GetCommandLineArgs()[i]);
                }
            }

            bool ipPbxService = false;

            // Add the installed services to the service list
            foreach (string srv in services)
            {
                try
                {
                    ServiceControllerEx sc = new ServiceControllerEx(srv);

                    // Property throws an exception, when the service is not installed
                    ServiceControllerStatus st = sc.Status;

                    if (st > 0 && !sc.IsDisabled())
                    {
                        // Add the service to the list of current installed services
                        this.serviceList.Add(sc);
                        
                        // Is the IpPbx service is installed?
                        if (srv == "IpPbxSrv")
                        {
                            ipPbxService = true;
                        }
                    }
                }
                catch(System.InvalidOperationException){ }
            }

            if (ipPbxService)
            {
                // IpPbx service is installed. Create a list of performance counters
                this.perfCounterList = new List<PerformanceCounterEx>();
                this.perfCounterList.Add(new PerformanceCounterEx("SwyxServer", "Logged in users"));
                this.perfCounterList.Add(new PerformanceCounterEx("SwyxServer", "Active external calls", "External calls"));
                this.perfCounterList.Add(new PerformanceCounterEx("SwyxServer", "Active internal calls", "Internal calls"));
            }
            else
            {
                this.perfCounterList = null;
            }
        }

        /// <summary>
        /// Update the list view with the current state of the installed services and the 
        /// monitored performance counters
        /// </summary>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
        private void UpdateServiceState()
        {
            try
            {
                bool statusChanged = false;

                // Proof the current state of all services
                foreach (ServiceControllerEx sc in this.serviceList)
                {
                    if (sc.StatusChanged())
                        statusChanged = true;
                }

                // Proof if a performance counter is changed
                if ((this.perfCounterList != null) && !statusChanged)
                {
                    foreach (PerformanceCounterEx pc in this.perfCounterList)
                    {
                        if (pc.Changed())
                            statusChanged = true;
                    }
                }

                // Don't refresh the output to avoid flickering
                if (!statusChanged)
                    return;

                ListViewGroup lvGroupService = this.listViewMain.Groups[1];
                ListViewGroup lvGroupIpPbx = this.listViewMain.Groups[2];
                ListView.ListViewItemCollection lvCol = this.ListViewColumn.ListView.Items;

                // Remove all items from the list view where the 
                // item is member of the group "service" or "IpPbx"
                foreach (ListViewItem lvI in lvCol)
                {
                    if ((lvI.Group == lvGroupService)||(lvI.Group == lvGroupIpPbx))
                        lvI.Remove();
                }

                // Add the current state of all services to the list
                foreach (ServiceControllerEx sc in this.serviceList)
                {
                    lvCol.Add(new ListViewItem(sc.DisplayName + ":", lvGroupService)).
                                   SubItems.Add(sc.Status.ToString());
                }

                // Add current values of performance counter to list
                if (this.perfCounterList != null)
                {
                    foreach (PerformanceCounterEx pc in this.perfCounterList)
                    {
                        lvCol.Add(new ListViewItem(pc.Description + ":", lvGroupIpPbx)).
                                   SubItems.Add(pc.RawValue.ToString());
                    }
                }

            }
            catch (InvalidOperationException)
            {
                // Handle service exception "Cannot open <name> service on computer ..."
                // Someone deletes a service. Reinitialize the list of installed services
                this.InitializeServiceList();
            }
            catch { }
        }

        /// <summary>
        /// Handler for the Close button.
        /// </summary>
        /// <param name="sender">Source of the event.</param>
        /// <param name="e">Details about the event.</param>
        private void buttonClose_Click(object sender, EventArgs e)
        {
            this.Close();
        }

        /// <summary>
        /// Handler for the timer thread
        /// Refresh the list of service states
        /// </summary>
        /// <param name="sender">Source of the event.</param>
        /// <param name="e">Details about the event.</param>
        private void TimerTick(object sender, EventArgs e)
        {
            // Refresh every 60 seconds the list of installed services and update the background color
            this.timerTickCount += this.timer.Interval;
            if (this.timerTickCount >= 60000)
            {
                this.timerTickCount = 0;
                this.InitializeServiceList();
                this.UpdateBackgroundColor();
            }

            // Update the service state 
            this.UpdateServiceState();
        }

        /// <summary>
        /// Handler for the "activated" event
        /// Send the main window to the undermost position and update the background
        /// </summary>
        /// <param name="sender">Source of the event.</param>
        /// <param name="e">Details about the event.</param>
        private void FormActivated(object sender, EventArgs e)
        {
            this.SendToBack();
            this.UpdateBackgroundColor();
        }


        // Offset of the "Move Point" button to the upper left corner of the window
        Point movePointOffset;

        /// <summary>
        /// Handle the mouse button down event of the "Move Point" button
        /// Calculate the move point offset
        /// </summary>
        /// <param name="sender">Source of the event.</param>
        /// <param name="e">Mouse move event.</param>
        private void buttonMove_MouseDown(object sender, MouseEventArgs e)
        {
            // Calculate the offset to the upper left corner of the window
            if (this.movePointOffset.IsEmpty)
                this.movePointOffset = new Point(this.DesktopLocation.X - Cursor.Position.X,
                                                 this.DesktopLocation.Y - Cursor.Position.Y);
        }

        /// <summary>
        /// Handle the mouse move event of the "Move Point" button
        /// Move the window to the current cursor position
        /// </summary>
        /// <param name="sender">Source of the event.</param>
        /// <param name="e">Mouse move event.</param>
        private void buttonMove_MouseMove(object sender, MouseEventArgs e)
        {
            if (e.Button != MouseButtons.Left)
                return;

            // Get the new position
            Point newPos = Cursor.Position;
            newPos.Offset(this.movePointOffset);

            // Move the window to the new position
            this.DesktopLocation = newPos;
        }

        /// <summary>
        /// Handle the mouse up event of the "Move Point" button
        /// Update the background color after the button is release
        /// </summary>
        /// <param name="sender">Source of the event.</param>
        /// <param name="e">Mouse move event.</param>
        private void buttonMove_MouseUp(object sender, MouseEventArgs e)
        {
            this.UpdateBackgroundColor();
        }

        /// <summary>
        /// Update the background color of all controls
        /// </summary>
        private void UpdateBackgroundColor()
        {
            Point p = this.PointToScreen(new Point(0,0));
            Color color = NativeMethods.GetPixelColor(p);

            this.BackColor = color;
            this.TransparencyKey = color;

            foreach (Control ctrl in this.Controls)
            {
                ctrl.BackColor = color;
            }
        }

        /// <summary>
        /// Read a IpPbx property using the Windows Installer API
        /// See MsiGetProductInfo() for more information
        /// </summary>
        /// <param name="msiPropertyName">Name of the property</param>
        /// <returns>
        /// Return the value of the property or
        /// null when IpPbx is not installed or the property is invalid
        /// </returns>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
        static string GetIpPbxMsiProductInfo(string msiPropertyName)
        {
            try
            {
                // Define the IpPbx upgrade code 
                const string guidIpPbx = "{F8E5535B-4C00-11D3-80BC-00105A653379}";

                StringBuilder productCode = new StringBuilder(40);

                // Use the installer API to get the related product code.
                // Only read the first product code
                NativeMethods.MsiEnumRelatedProductsReturn msiProductsReturn =
                    NativeMethods.MsiEnumRelatedProducts(guidIpPbx, 0, 0, productCode);

                // Product found?
                if ((msiProductsReturn != NativeMethods.MsiEnumRelatedProductsReturn.ErrorSuccess) ||
                     (productCode.Length == 0))
                    return null;

                uint len = 0;

                // Read the installer property. First get the length of the property
                NativeMethods.MsiGetProductInfoReturn msiInfoReturn =
                    NativeMethods.MsiGetProductInfo(productCode.ToString(), msiPropertyName, null, ref len);

                // Property valid?
                if (msiInfoReturn != NativeMethods.MsiGetProductInfoReturn.ErrorSuccess)
                    return null;

                // Create a string builder object with required length
                len++;
                StringBuilder sbProperty = new StringBuilder((int)len);

                // Read the installer property. 
                msiInfoReturn = NativeMethods.MsiGetProductInfo(productCode.ToString(), msiPropertyName, sbProperty, ref len);

                // Property valid?
                if (msiInfoReturn != NativeMethods.MsiGetProductInfoReturn.ErrorSuccess)
                    return null;

                return sbProperty.ToString();
            }
            catch { }

            return null;
        }

        /// <summary>
        /// Get a pixel color from the screen coordinate
        /// </summary>
        private sealed class NativeMethods
        {
            /// <summary>MsiEnumRelatedProducts return values</summary>
            public enum MsiEnumRelatedProductsReturn : uint
            {
                /// <summary>ERROR_BAD_CONFIGURATION: The configuration data for this product is corrupt.</summary>
                ErrorBadConfiguration = 1610,
                /// <summary>ERROR_INVALID_PARAMETER: An invalid parameter was passed to the function.</summary>
                ErrorInvalidParameter = 87,
                /// <summary>ERROR_NO_MORE_ITEMS: There are no products to return.</summary>
                ErrorNoMoreItems = 259,
                /// <summary>ERROR_NOT_ENOUGH_MEMORY: The system does not have enough memory to complete the operation.</summary>
                ErrorNotEnoughMemory = 8,
                /// <summary>ERROR_SUCCESS: A value was enumerated.</summary>
                ErrorSuccess = 0
            }

            /// <summary>
            /// Enumerate products with given upgrade code
            /// </summary>
            /// <param name="UpgradeCode">upgrade code of related products that the installer is to enumerate</param>
            /// <param name="dwReserved">reserved, must be 0</param>
            /// <param name="iProductIndex">0-based index into registered products</param>
            /// <param name="ProductBuffer">Buffer to receive the product code GUID. StringBuilder object must have a size of min. 39 chars</param>
            /// <returns>Enum value of MsiEnumRelatedProductsReturn</returns>
            [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Globalization", "CA2101:SpecifyMarshalingForPInvokeStringArguments", MessageId = "3"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Globalization", "CA2101:SpecifyMarshalingForPInvokeStringArguments", MessageId = "0"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1060:MovePInvokesToNativeMethodsClass"), DllImport("msi.dll", CharSet = CharSet.Auto)]
            public static extern MsiEnumRelatedProductsReturn MsiEnumRelatedProducts
                    (string UpgradeCode, uint dwReserved, uint iProductIndex, [Out] StringBuilder msiProductBuffer);

            /// <summary>MsiGetProductInfo return values</summary>
            public enum MsiGetProductInfoReturn : uint
            {
                /// <summary>ERROR_BAD_CONFIGURATION: The configuration data for this product is corrupt.</summary>
                ErrorBadConfiguration = 1610,
                /// <summary>ERROR_INVALID_PARAMETER: An invalid parameter was passed to the function.</summary>
                ErrorInvalidParameter = 87,
                /// <summary>ERROR_MORE_DATA: A buffer is too small to hold the requested data.</summary>
                ErrorMoreData = 234,
                /// <summary>ERROR_UNKNOWN_PRODUCT: The product is unadvertised or uninstalled. </summary>
                ErrorUnknownProduct = 1605,
                /// <summary>ERROR_UNKNOWN_PROPERTY The property is unrecognized.</summary>
                ErrorUnknownProperty = 1608,
                /// <summary>ERROR_SUCCESS: A value was enumerated.</summary>
                ErrorSuccess = 0
            }

            /// <summary>
            /// Get information for published and installed products
            /// </summary>
            /// <param name="product">Specifies the product code for the product.</param>
            /// <param name="property">Specifies the property to be retrieved.</param>
            /// <param name="valueBuf">Property value</param>
            /// <param name="len">Property value len</param>
            /// <returns>Enum value of MsiEnumRelatedProductsReturn</returns>
            [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Globalization", "CA2101:SpecifyMarshalingForPInvokeStringArguments", MessageId = "2"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Globalization", "CA2101:SpecifyMarshalingForPInvokeStringArguments", MessageId = "1"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Globalization", "CA2101:SpecifyMarshalingForPInvokeStringArguments", MessageId = "0"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1060:MovePInvokesToNativeMethodsClass"), DllImport("msi.dll", CharSet = CharSet.Auto)]
            public static extern MsiGetProductInfoReturn MsiGetProductInfo
                (string product, string property, [Out] StringBuilder valueBuf, ref uint len);

            /// <summary>
            /// The GetDC function retrieves a handle to a device context (DC) for the client 
            /// area of a specified window or for the entire screen.
            /// </summary>
            /// <param name="hwnd">A handle to the window whose DC is to be retrieved.</param>
            /// <returns>
            /// If the function succeeds, the return value is a handle to the DC 
            /// for the specified window's client area.
            /// If the function fails, the return value is NULL.
            /// </returns>
            [DllImport("user32.dll")]
            static extern IntPtr GetDC(IntPtr hwnd);

            /// <summary>
            /// The ReleaseDC function releases a device context (DC), freeing it for use by other applications.
            /// </summary>
            /// <param name="hwnd">A handle to the window whose DC is to be released.</param>
            /// <param name="hdc">A handle to the DC to be released.</param>
            /// <returns>
            /// The return value indicates whether the DC was released. 
            /// If the DC was released, the return value is 1. 
            /// If the DC was not released, the return value is zero.
            /// </returns>
            [DllImport("user32.dll")]
            static extern Int32 ReleaseDC(IntPtr hwnd, IntPtr hdc);

            /// <summary>
            /// The GetPixel function retrieves the red, green, blue (RGB) 
            /// color value of the pixel at the specified coordinates.
            /// </summary>
            /// <param name="hdc">A handle to the device context.</param>
            /// <param name="nXPos">The x-coordinate, in logical units, of the pixel to be examined.</param>
            /// <param name="nYPos">The y-coordinate, in logical units, of the pixel to be examined.</param>
            /// <returns>
            /// The return value is the COLORREF value that specifies the RGB of the pixel.
            /// </returns>
            [DllImport("gdi32.dll")]
            static extern uint GetPixel(IntPtr hdc, int nXPos, int nYPos);

            /// <summary>
            /// Get a pixel color from the screen coordinate
            /// </summary>
            /// <param name="point">Point in screen coordinate</param>
            /// <returns>
            /// The return value is the System.Drawing.Color value that 
            /// specifies the color of the pixel.
            /// </returns>
            static public System.Drawing.Color GetPixelColor(Point point)
            {
                IntPtr hdc = GetDC(IntPtr.Zero);
                uint pixel = GetPixel(hdc, point.X, point.Y);
                ReleaseDC(IntPtr.Zero, hdc);
                Color color = Color.FromArgb((int)(pixel & 0x000000FF),
                             (int)(pixel & 0x0000FF00) >> 8,
                             (int)(pixel & 0x00FF0000) >> 16);
                return color;
            }
        }

       
    }
}